{
  Copyright 2012 Sergey Ostanin

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
}

unit ProfileList;

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ComCtrls,
  ActnList, Menus, Shape, VisualUtils, superobject, Profile, ItParser, base64,
  ItImport, ProfileEditor, ItXmlizer, MiscUtils, ItExport;

type
  TProfileListFrame = class(TShapeFrame)
    actEdit: TAction;
    actAdd: TAction;
    actDelete: TAction;
    actMoveDown: TAction;
    actMoveUp: TAction;
    alProfiles: TActionList;
    ApplicationProperties: TApplicationProperties;
    ilProfiles: TImageList;
    lvProfiles: TListView;
    miDelete: TMenuItem;
    miSeparator2: TMenuItem;
    miMoveDown: TMenuItem;
    miMoveUp: TMenuItem;
    miSeparator1: TMenuItem;
    miEdit: TMenuItem;
    miAdd: TMenuItem;
    pmProfiles: TPopupMenu;
    tbrProfiles: TToolBar;
    tbAdd: TToolButton;
    tbDelete: TToolButton;
    tbEdit: TToolButton;
    tbSeparator1: TToolButton;
    tbSeparator2: TToolButton;
    tbMoveUp: TToolButton;
    tbMoveDown: TToolButton;
    procedure actAddExecute(Sender: TObject);
    procedure actDeleteExecute(Sender: TObject);
    procedure actDeleteUpdate(Sender: TObject);
    procedure actEditExecute(Sender: TObject);
    procedure actEditUpdate(Sender: TObject);
    procedure actMoveDownExecute(Sender: TObject);
    procedure actMoveDownUpdate(Sender: TObject);
    procedure actMoveUpExecute(Sender: TObject);
    procedure actMoveUpUpdate(Sender: TObject);
    procedure ApplicationPropertiesIdle(Sender: TObject; var Done: Boolean);
    procedure lvProfilesDblClick(Sender: TObject);
    procedure lvProfilesResize(Sender: TObject);
    procedure lvProfilesSelectItem(Sender: TObject; Item: TListItem;
      Selected: Boolean);
  private
    FSelection: ISuperObject;
    FRendered: Boolean;
    FSelectedByUser: Boolean;
    function GetSelectedIndex: Integer;
    function HasDefaultProfileOnly: Boolean;
    function GenerateNewProfileTitle: String;
    procedure MoveProfile(Down: Boolean);
    { private declarations }
  public
    procedure AfterConstruction; override;
    procedure Render;
    { public declarations }
  end;

implementation

{$R *.lfm}
{$R *.rc}

resourcestring
  SDefaultProfileTitle = '<default>';
  SNewProfileTitle = 'Profile %d';
  SAdd = 'Add';

{ TProfileListFrame }

procedure TProfileListFrame.ApplicationPropertiesIdle(Sender: TObject;
  var Done: Boolean);
var
  SelectedIndex: Integer;
  Item: TListItem;
  Input: ISuperObject;
begin
  SelectedIndex := lvProfiles.ItemIndex;
  if (SelectedIndex = -1) or not FSelectedByUser then
  begin
    Item := lvProfiles.Items[GetSelectedIndex];
    Item.Selected := TRUE;
    lvProfiles.ItemFocused := Item;
  end
  else if SelectedIndex <> GetSelectedIndex then
  begin
    Input := SO;
    Input.I['index'] := SelectedIndex;
    Dispatcher.PerformAction(Id, 'select', Input);
  end;

  FSelectedByUser := FALSE;
end;

procedure TProfileListFrame.actEditExecute(Sender: TObject);
var
  Profile: TProfile;
  Input: ISuperObject;
begin
  Profile := BytesToProfile(DecodeStringBase64(
    Dispatcher.PerformAction(Id, 'getSelected').S['profile']));
  try
    if TProfileEditorForm.OpenModal(Profile) then
    begin
      Input := SO;
      Input.S['profile'] := EncodeStringBase64(ProfileToBytes(Profile));
      Dispatcher.PerformAction(Id, 'setSelected', Input);
    end;
  finally
    Profile.Free;
  end;
end;

procedure TProfileListFrame.actEditUpdate(Sender: TObject);
begin
  (Sender as TAction).Enabled := not HasDefaultProfileOnly;
end;

procedure TProfileListFrame.actMoveDownExecute(Sender: TObject);
begin
  MoveProfile(TRUE);
end;

procedure TProfileListFrame.actMoveDownUpdate(Sender: TObject);
begin
  (Sender as TAction).Enabled := not HasDefaultProfileOnly
    and not lvProfiles.Items[lvProfiles.Items.Count-1].Selected;
end;

procedure TProfileListFrame.actMoveUpExecute(Sender: TObject);
begin
  MoveProfile(FALSE);
end;

procedure TProfileListFrame.actMoveUpUpdate(Sender: TObject);
begin
  (Sender as TAction).Enabled := not HasDefaultProfileOnly
    and not lvProfiles.Items[0].Selected;
end;

procedure TProfileListFrame.actAddExecute(Sender: TObject);
var
  Profile: TProfile;
  Input: ISuperObject;
begin
  Profile := BytesToProfile(DecodeStringBase64(
    Dispatcher.PerformAction(Id, 'getDefault').S['profile']));
  try
    Profile.Title := GenerateNewProfileTitle;
    if TProfileEditorForm.OpenModal(Profile, SAdd) then
    begin
      Input := SO;
      Input.S['profile'] := EncodeStringBase64(ProfileToBytes(Profile));
      Dispatcher.PerformAction(Id, 'add', Input);
    end;
  finally
    Profile.Free;
  end;
end;

procedure TProfileListFrame.actDeleteExecute(Sender: TObject);
begin
  Dispatcher.PerformAction(Id, 'delete');
end;

procedure TProfileListFrame.actDeleteUpdate(Sender: TObject);
begin
  (Sender as TAction).Enabled := not HasDefaultProfileOnly;
end;

procedure TProfileListFrame.lvProfilesDblClick(Sender: TObject);
var
  p: TPoint;
begin
  p := lvProfiles.ScreenToClient(Mouse.CursorPos);
  if lvProfiles.GetItemAt(p.x, p.y) <> nil then
  begin
    if HasDefaultProfileOnly then
      actAdd.Execute
    else
      actEdit.Execute;
  end;
end;

procedure TProfileListFrame.lvProfilesResize(Sender: TObject);
begin
  AutoSizeListViewColumn(lvProfiles, 0);
end;

procedure TProfileListFrame.lvProfilesSelectItem(Sender: TObject;
  Item: TListItem; Selected: Boolean);
begin
  FSelectedByUser := TRUE;
end;

function TProfileListFrame.GetSelectedIndex: Integer;
begin
  if HasDefaultProfileOnly then
    Result := 0
  else
    Result := FSelection.I['selectedIndex'];
end;

function TProfileListFrame.HasDefaultProfileOnly: Boolean;
begin
  Result := Shape.A['title'].Length = 0;
end;

function TProfileListFrame.GenerateNewProfileTitle: String;
var
  n: Integer;
  AlreadyExists: Boolean;
  Title: ISuperObject;
  s: UnicodeString;
begin
  n := 0;
  repeat
    Inc(n);
    Result := Format(SNewProfileTitle, [n]);
    s := UTF8Decode(Result);

    AlreadyExists := FALSE;
    for Title in Shape['title'] do
      if Title.AsString = s then
      begin
        AlreadyExists := TRUE;
        Break;
      end;
  until not AlreadyExists;
end;

procedure TProfileListFrame.MoveProfile(Down: Boolean);
var
  Input: ISuperObject;
begin
  Input := SO;
  Input.B['forward'] := Down;
  Dispatcher.PerformAction(Id, 'move', Input);
end;

procedure TProfileListFrame.AfterConstruction;
begin
  inherited;
  alProfiles.OnExecute := Scene.ActionListExecuteFilter;
  LoadActionImages(alProfiles);
  AppendMenuShortcutsToActionHints(pmProfiles);

  MakeListViewDoubleBuffered(lvProfiles);
end;

procedure TProfileListFrame.Render;
var
  ShapeChanged: Boolean;
  Titles: TSuperArray;
  i: Integer;
begin
  ShapeChanged := UpdateShape;
  Scene.Update(Shape.S['selection'], FSelection);

  if not FRendered or ShapeChanged then
  begin
    LockRedraw(Self);
    try
      if HasDefaultProfileOnly then
      begin
        SetListViewItemCount(lvProfiles, 1);
        lvProfiles.Items[0].Caption := SDefaultProfileTitle;
      end
      else
      begin
        Titles := Shape.A['title'];
        SetListViewItemCount(lvProfiles, Titles.Length);
        for i := 0 to Titles.Length-1 do
          lvProfiles.Items[i].Caption := Titles.S[i];
      end;

      lvProfiles.Items[GetSelectedIndex].MakeVisible(FALSE);
      FSelectedByUser := FALSE;
    finally
      UnlockRedraw(Self);
    end;
  end;

  FRendered := TRUE;
end;

end.

